<html><head><meta charset="utf-8"><title>34 只求问倒：连环相扣系列锁面试题 -慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        34 只求问倒：连环相扣系列锁面试题 
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-11-13 11:49:45
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img4.mukewang.com/5dcb6b32000139a906400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">自信和希望是青年的特权。<p class="author">——大仲马</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h2 id="引导语" style="font-size: 30px;">引导语</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">面试中，问锁主要是两方面：锁的日常使用场景 + 锁原理，锁的日常使用场景主要考察对锁 API 的使用熟练度，看看你是否真的使用过这些 API，而不是纸上谈兵，锁原理主要就是问 AQS 底层的源码原理了，如果问得更加深入的话，可能会现场让你实现一个简单的锁，简单要求的会让你直接使用 AQS API，复杂要求的可能需要重新实现 AQS。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">接下来我们一起看一看关于锁的常见源码面试题。</p>
</div><div class="cl-preview-section"><h2 id="aqs-相关面试题" style="font-size: 30px;">1 AQS 相关面试题</h2>
</div><div class="cl-preview-section"><h3 id="说说自己对-aqs-的理解？">1.1 说说自己对 AQS 的理解？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：回答这样的问题的时候，面试官主要考察的是你对 AQS 的知识有没有系统的整理，建议回答的方向是由大到小，由全到细，由使用到原理。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">如果和面试官面对面的话，可以边说边画出我们在 AQS 源码解析上中画出的整体架构图，并且可以这么说：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">AQS 是一个锁框架，它定义了锁的实现机制，并开放出扩展的地方，让子类去实现，比如我们在 lock 的时候，AQS 开放出 state 字段，让子类可以根据 state 字段来决定是否能够获得锁，对于获取不到锁的线程 AQS 会自动进行管理，无需子类锁关心，这就是 lock 时锁的内部机制，封装的很好，又暴露出子类锁需要扩展的地方；</li>
<li style="font-size: 20px; line-height: 38px;">AQS 底层是由同步队列 + 条件队列联手组成，同步队列管理着获取不到锁的线程的排队和释放，条件队列是在一定场景下，对同步队列的补充，比如获得锁的线程从空队列中拿数据，肯定是拿不到数据的，这时候条件队列就会管理该线程，使该线程阻塞；</li>
<li style="font-size: 20px; line-height: 38px;">AQS 围绕两个队列，提供了四大场景，分别是：获得锁、释放锁、条件队列的阻塞，条件队列的唤醒，分别对应着 AQS 架构图中的四种颜色的线的走向。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">以上三点都是 AQS 全局方面的描述，接着你可以问问面试官要不要说细一点，可以的话，按照 AQS 源码解析上下两篇，把四大场景都说一下就好了。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这样说的好处是很多的：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">面试的主动权把握在自己手里，而且都是自己掌握的知识点；</li>
<li style="font-size: 20px; line-height: 38px;">由全到细的把 AQS 全部说完，会给面试官一种你对 AQS 了如指掌的感觉，再加上全部说完耗时会很久，面试时间又很有限，面试官就不会再问关于 AQS 一些刁钻的问题了，这样 AQS 就可以轻松过关。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">当然如果你对 AQS 了解的不是很深，那么就大概回答下 AQS 的大体架构就好了，就不要说的特别细，免得给自己挖坑。</p>
</div><div class="cl-preview-section"><h3 id="多个线程通过锁请求共享资源，获取不到锁的线程怎么办？">1.2 多个线程通过锁请求共享资源，获取不到锁的线程怎么办？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：加锁(排它锁)主要分为以下四步：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">尝试获得锁，获得锁了直接返回，获取不到锁的走到 2；</li>
<li style="font-size: 20px; line-height: 38px;">用 Node 封装当前线程，追加到同步队列的队尾，追加到队尾时，又有两步，如 3 和 4；</li>
<li style="font-size: 20px; line-height: 38px;">自旋 + CAS 保证前一个节点的状态置为 signal；</li>
<li style="font-size: 20px; line-height: 38px;">阻塞自己，使当前线程进入等待状态。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">获取不到锁的线程会进行 2、3、4 步，最终会陷入等待状态，这个描述的是排它锁。</p>
</div><div class="cl-preview-section"><h3 id="问题-1.2-中，排它锁和共享锁的处理机制是一样的么？">1.3 问题 1.2 中，排它锁和共享锁的处理机制是一样的么？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：排它锁和共享锁在问题 1.2 中的 2、3、4 步骤都是一样的， 不同的是在于第一步，线程获得排它锁的时候，仅仅把自己设置为同步队列的头节点即可，但如果是共享锁的话，还会去唤醒自己的后续节点，一起来获得该锁。</p>
</div><div class="cl-preview-section"><h3 id="共享锁和排它锁的区别？">1.4 共享锁和排它锁的区别？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：排它锁的意思是同一时刻，只能有一个线程可以获得锁，也只能有一个线程可以释放锁。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">共享锁可以允许多个线程获得同一个锁，并且可以设置获取锁的线程数量，共享锁之所以能够做到这些，是因为线程一旦获得共享锁，把自己设置成同步队列的头节点后，会自动的去释放头节点后等待获取共享锁的节点，让这些等待节点也一起来获得共享锁，而排它锁就不会这么干。</p>
</div><div class="cl-preview-section"><h3 id="排它锁和共享锁说的是加锁时的策略，那么锁释放时有排它锁和共享锁的策略么？">1.5 排它锁和共享锁说的是加锁时的策略，那么锁释放时有排它锁和共享锁的策略么？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：是的，排它锁和共享锁，主要体现在加锁时，多个线程能否获得同一个锁。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">但在锁释放时，是没有排它锁和共享锁的概念和策略的，概念仅仅针对锁获取。</p>
</div><div class="cl-preview-section"><h3 id="描述下同步队列？">1.6 描述下同步队列？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：同步队列底层的数据结构就是双向的链表，节点叫做 Node，头节点叫做 head，尾节点叫做 tail，节点和节点间的前后指向分别叫做 prev、next，如果是面对面面试的话，可以画一下 AQS 整体架构图中的同步队列。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">同步队列的作用：阻塞获取不到锁的线程，并在适当时机释放这些线程。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">实现的大致过程：当多个线程都来请求锁时，某一时刻有且只有一个线程能够获得锁（排它锁），那么剩余获取不到锁的线程，都会到同步队列中去排队并阻塞自己，当有线程主动释放锁时，就会从同步队列中头节点开始释放一个排队的线程，让线程重新去竞争锁。</p>
</div><div class="cl-preview-section"><h3 id="描述下线程入、出同步队列的时机和过程？">1.7 描述下线程入、出同步队列的时机和过程？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：(排它锁为例)从 AQS 整体架构图中，可以看出同步队列入队和出队都是有两个箭头指向，所以入队和出队的时机各有两个。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">同步队列入队时机：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">多个线程请求锁，获取不到锁的线程需要到同步队列中排队阻塞；</li>
<li style="font-size: 20px; line-height: 38px;">条件队列中的节点被唤醒，会从条件队列中转移到同步队列中来。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">同步队列出队时机：</p>
</div><div class="cl-preview-section"><ol start="3">
<li style="font-size: 20px; line-height: 38px;">锁释放时，头节点出队；</li>
<li style="font-size: 20px; line-height: 38px;">获得锁的线程，进入条件队列时，会释放锁，同步队列头节点开始竞争锁。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">四个时机的过程可以参考 AQS 源码解析，1 参考 acquire 方法执行过程，2 参考 signal 方法，3 参考 release 方法，4 参考 await 方法。</p>
</div><div class="cl-preview-section"><h3 id="为什么-aqs-有了同步队列之后，还需要条件队列？">1.8 为什么 AQS 有了同步队列之后，还需要条件队列？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：的确，一般情况下，我们只需要有同步队列就好了，但在上锁后，需要操作队列的场景下，一个同步队列就搞不定了，需要条件队列进行功能补充，比如当队列满时，执行 put 操作的线程会进入条件队列等待，当队列空时，执行 take 操作的线程也会进入条件队列中等待，从一定程度上来看，条件队列是对同步队列的场景功能补充。</p>
</div><div class="cl-preview-section"><h3 id="描述一下条件队列中的元素入队和出队的时机和过程？">1.9 描述一下条件队列中的元素入队和出队的时机和过程？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：入队时机：执行 await 方法时，当前线程会释放锁，并进入到条件队列。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">出队时机：执行 signal、signalAll 方法时，节点会从条件队列中转移到同步队列中。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">具体的执行过程，可以参考源码解析中 await 和 signal 方法。</p>
</div><div class="cl-preview-section"><h3 id="描述一下条件队列中的节点转移到同步队列中去的时机和过程？">1.10 描述一下条件队列中的节点转移到同步队列中去的时机和过程？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：时机：当有线程执行 signal、signalAll 方法时，从条件队列的头节点开始，转移到同步队列中去。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">过程主要是以下几步：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">找到条件队列的头节点，头节点 next 属性置为 null，从条件队列中移除了；</li>
<li style="font-size: 20px; line-height: 38px;">头节点追加到同步队列的队尾；</li>
<li style="font-size: 20px; line-height: 38px;">头节点状态（waitStatus）从 CONDITION 修改成 0（初始化状态）；</li>
<li style="font-size: 20px; line-height: 38px;">将节点的前一个节点状态置为 SIGNAL。</li>
</ol>
</div><div class="cl-preview-section"><h3 id="线程入条件队列时，为什么需要释放持有的锁？">1.11 线程入条件队列时，为什么需要释放持有的锁？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：原因很简单，如果当前线程不释放锁，一旦跑去条件队里中阻塞了，后续所有的线程都无法获得锁，正确的场景应该是：当前线程释放锁，到条件队列中去阻塞后，其他线程仍然可以获得当前锁。</p>
</div><div class="cl-preview-section"><h2 id="aqs-子类锁面试题" style="font-size: 30px;">2 AQS 子类锁面试题</h2>
</div><div class="cl-preview-section"><h3 id="你在工作中如何使用锁的，写一个看一看？">2.1 你在工作中如何使用锁的，写一个看一看？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：这个照实说就好了，具体 demo 可以参考：demo.sixth.ConditionDemo。</p>
</div><div class="cl-preview-section"><h3 id="如果我要自定义锁，大概的实现思路是什么样子的？">2.1 如果我要自定义锁，大概的实现思路是什么样子的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：现在有很多类似的问题，比如让你自定义队列，自定义锁等等，面试官其实并不是想让我们重新造一个轮子，而是想考察一下我们对于队列、锁理解的深度，我们只需要选择自己最熟悉的 API 描述一下就好了，所以这题我们可以选择 ReentrantLock 来描述一下实现思路：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">新建内部类继承 AQS，并实现 AQS 的 tryAcquire 和 tryRelease 两个方法，在 tryAcquire 方法里面实现控制能否获取锁，比如当同步器状态 state 是 0 时，即可获得锁，在 tryRelease 方法里面控制能否释放锁，比如将同步器状态递减到 0 时，即可释放锁；</li>
<li style="font-size: 20px; line-height: 38px;">对外提供 lock、release 两个方法，lock 表示获得锁的方法，底层调用 AQS 的 acquire 方法，release 表示释放锁的方法，底层调用 AQS 的 release 方法。</li>
</ol>
</div><div class="cl-preview-section"><h3 id="描述-reentrantlock-两大特性：可重入性和公平性？底层分别如何实现的？">2.2 描述 ReentrantLock 两大特性：可重入性和公平性？底层分别如何实现的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：可重入性说的是线程可以对共享资源重复加锁，对应的，释放时也可以重复释放，对于 ReentrantLock 来说，在获得锁的时候，state 会加 1，重复获得锁时，不断的对 state 进行递增即可，比如目前 state 是 4，表示线程已经对共享资源加锁了 4 次，线程每次释放共享资源的锁时，state 就会递减 1，直到递减到 0 时，才算真正释放掉共享资源。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">公平性和非公平指的是同步队列中的线程得到锁的机制，如果同步队列中的线程按照阻塞的顺序得到锁，我们称之为公平的，反之是非公平的，公平的底层实现是 ReentrantLock 的 tryAcquire 方法（调用的是 AQS 的 hasQueuedPredecessors 方法）里面实现的，要释放同步队列的节点时（或者获得锁时），判断当前线程节点是不是同步队列的头节点的后一个节点，如果是就释放，不是则不能释放，通过这种机制，保证同步队列中的线程得到锁时，是按照从头到尾的顺序的。</p>
</div><div class="cl-preview-section"><h3 id="如果一个线程需要等待一组线程全部执行完之后再继续执行，有什么好的办法么？是如何实现的？">2.3 如果一个线程需要等待一组线程全部执行完之后再继续执行，有什么好的办法么？是如何实现的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答：CountDownLatch 就提供了这样的机制，比如一组线程有 5 个，只需要在初始化 CountDownLatch 时，给同步器的 state 赋值为 5，主线程执行 CountDownLatch.await ，子线程都执行 CountDownLatch.countDown 即可。</p>
</div><div class="cl-preview-section"><h3 id="atomic-原子操作类可以保证线程安全，如果操作的对象是自定义的类的话，要如何做呢？">2.4 Atomic 原子操作类可以保证线程安全，如果操作的对象是自定义的类的话，要如何做呢？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">答： Java 为这种情况提供了一个 API：AtomicReference，AtomicReference 类可操作的对象是个泛型，所以支持自定义类。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">3 总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">关于 AQS 和锁场景的面试题，其实网上也很多，各个大厂出的题目也都不一样，但考察问题的本质都是一致的，如果把 AQS 架构图中，AQS 的组成和四种颜色箭头的发起时机，调用过程都弄清楚了，回答 AQS 的各种问题都会游刃有余。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/875">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            33 CountDownLatch、Atomic 等其它源码解析     
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/877">
                                                                    <div class="next r clearfix">
                                        <p>
                                            35 经验总结：各种锁在工作中使用场景和细节    
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img2.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>